/*
 * Copyright (c) 2014. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

package net.NettyEngine4;

import com.dc.gameserver.ServerCore.Controller.AbstractController.IController;
import com.dc.gameserver.ServerCore.Service.character.PlayerInstance;
import com.google.protobuf.ExtensionRegistryLite;
import com.google.protobuf.MessageLite;
import com.google.protobuf.UninitializedMessageException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;

import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author :石头哥哥<br/>
 *         Project:CreazyGameServer1.8
 *         Date: 13-4-12
 *         Time: 上午9:19
 *
 *         ChannelStateHandlerAdapter
 *         ChannelInboundByteHandlerAdapter
 *         ChannelDuplexHandler
 *         ChannelInboundHandlerAdapter
 *
 * This implementation just forward the operation to the next {@link io.netty.channel.ChannelHandler} in the
 * {@link io.netty.channel.ChannelPipeline}. Sub-classes may override a method implementation to change this.
 *
 *
 *    I/O线程池递交给逻辑处理线程池
 *
 *     I/O线程池      处理线程池   数据DB线程池
 *
 *     EVENT_EXECUTORS 线程池处理数据  ServerHandler一系列事件
 *     逻辑计算数据  和数据库逻辑分开
 *
 *
 *
 *    如果channel断开连接：1.逻辑线程会继续处理余下的任务，调用ChannelRead()方法，在inactive状态下，buf将会被回收；
 *                                       2.inactive，将会做一些清理的工作，同样如果 RecyclableArrayList 对象中有buf也将会调用 调用ChannelRead()方法
 *                                       将其释放掉；
 *                                       3.  每一个channel 对应 一个RecyclableArrayList对象，作为该channel的缓冲数据队列；
 *
 *
 */
public class ServerHandler extends ChannelInboundHandlerAdapter {

    public static final Logger logger =
            LoggerFactory.getLogger(ServerHandler.class);

    /**记录服务器在线人数--连接数*/
    private static final AtomicInteger countConnection=new AtomicInteger(0);

    public static BeanFactory springContext;  //spring context   manager by spring

    private PlayerInstance player;

    public ServerHandler(){}

    /**
     *  添加监听器，在指定的时间发送消息
     * Calls {@link io.netty.channel.ChannelHandlerContext#fireChannelActive()} to forward
     * to the next {@link io.netty.channel.ChannelInboundHandler} in the {@link io.netty.channel.ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        countConnection.getAndIncrement();// +1
        player = (PlayerInstance) ServerHandler.springContext.getBean("player");
       // player.setChannel(ctx.channel());
        if (logger.isDebugEnabled())logger.debug(ctx.channel()+"connect server... Concurrent  connection... " + countConnection.get());
    }

    /**
     * Calls {@link io.netty.channel.ChannelHandlerContext#fireChannelRead(Object)} to forward
     * to the next {@link io.netty.channel.ChannelInboundHandler} in the {@link io.netty.channel.ChannelPipeline}.
     * 每一个channel 对应 一个RecyclableArrayList对象，作为该channel的缓冲数据队列；
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {
        ByteBuf msg=(ByteBuf) message;
        /**
         *     if channel inactive  ,the finally  callback method{@link io.netty.handler.codec.ByteToMessageDecoder#channelInactive(io.netty.channel.ChannelHandlerContext)}
         *     this method will do some clear work ,if Recycle ArrayList is not empty ,so will invoke method channelRead,and release byteBuffer,
         *     every task(byteBuffer data which was store blockingQueue will be executor by the "逻辑线程池" ,no matter what it's status)  ,
         *     so if the channel  inactive ,we will clear some buf ,in case of memory crash application,like this :
         */
        if (!ctx.channel().isActive()){     //inactive status , release buffer
            msg.release();
            msg=null;
        } else {
            try{
                /**类型ID**/
                int ID=msg.readInt();
                /**将byte[]转化为pb实体映射数据**/
                int length = msg.readableBytes();
                if (length!=0){
                    byte[] array=new byte[length];
                    msg.getBytes(msg.readerIndex(), array, 0, length);
                    msg.release();  //release byte buffer
                    msg=null;  //collection by GC directly
                    MessageLite messageLite=
                            IController.MESSAGE_LITE[ID].getParserForType().parseFrom(array,0,length, ExtensionRegistryLite.getEmptyRegistry());
                    array=null; //collection by GC directly
                    logger.debug("接收到消息 &( ^___^ )&  -->"+messageLite.toString()+"ID:"+ ID);
                    IController.GAME_CONTROLLERS[ID].DispatchMessageLit(messageLite, player);
                } else {
                    msg.release();
                    msg=null;
                    /**消息实体为null*/
                    IController.GAME_CONTROLLERS[ID].DispatchMessageLit(null, player);
                }
            }catch (UninitializedMessageException e){
                e.printStackTrace();
            }
        }
    }
    /**
     * Calls {@link io.netty.channel.ChannelHandlerContext#fireChannelInactive()} to forward
     * to the next {@link io.netty.channel.Channel} in the {@link io.netty.channel.ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     *
     * 注意回收资源
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx){
        try {
            ctx.channel().close();
            //clear and store data!  channel closed!
           // this.player.saveAndDestory();

            countConnection.decrementAndGet();// -1

            if (logger.isDebugEnabled())logger.debug(ctx.channel().remoteAddress()+" channelClosed , Concurrent  connection... " + countConnection.get());

            ChannelPipeline channelPipeline=ctx.channel().pipeline();

            if (null==channelPipeline){   // if null ,will be returned
                return;
            }
            //remove pipeline object
            while (channelPipeline.last()!=null){
                channelPipeline.removeLast();
            }
            if (ctx.isRemoved()){
                ctx.close();
                // Set to null so the GC can collect them directly
                channelPipeline=null;
                ctx=null;
                player=null;
            }
        }catch (NoSuchElementException ee){
            //do nothing
        } catch (Exception e) {
            if (logger.isDebugEnabled())logger.error("",e);
            //do nothing
        }
    }
    /**
     * 心跳处理
     * 链路读写超时处理
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception{
        Channel channel=ctx.channel();
        if (evt instanceof IdleStateEvent){
            IdleStateEvent e= (IdleStateEvent) evt;
            //No data was received for a while.
            if (e.state()== IdleState.READER_IDLE){
                //you can do sth ,such as timeout

                channel.close();  //call back channelInactive(ChannelHandlerContext ctx)
                if (logger.isDebugEnabled())logger
                        .debug(channel.remoteAddress()+"---No data was received for a while ,read time out... ...");
            } //	  because we are attaching  more importance to the read_idle time(timeout to rec)
            else  if (e.state()== IdleState.WRITER_IDLE){ // No data was sent for a while.

                channel.close();
                if (logger.isDebugEnabled()) logger
                        .debug(channel.remoteAddress()+"---No data was sent for a while.write time out... ...");
            }
        }
    }

    /**
     * 获取在线人数
     * @return player  num
     */
    public static int getOnlinePlayers(){
        return countConnection.get();
    }

    /**
     * Sub-classes may override this method to change behavior.
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        cause.printStackTrace();
//        try {
////            ctx.channel().close();
////            ctx.close();
//        }catch (Exception e){
//            e.printStackTrace();
//        }

    }

    /**spring context**/
    public static void setSpringContext(BeanFactory springContext) {
        ServerHandler.springContext = springContext;
    }


}
